package com.ztom.agent.asm.plugin.http.servletrequest;

import com.google.common.base.Strings;
import com.ztom.agent.annotation.InjectBeginMethod;
import com.ztom.agent.annotation.InjectClass;
import com.ztom.agent.annotation.InjectEndMethod;
import com.ztom.agent.asm.BaseAdviceAdapter;
import com.ztom.agent.bean.InjectClassInfo;
import com.ztom.agent.trace.Span;
import com.ztom.agent.trace.Tracer;
import com.ztom.agent.log.Logger;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import java.util.HashMap;
import java.util.Map;

/**
 * 用于注入HttpServlet类处理HttpServletRequest的插件类
 *
 * @author ZhangTao
 */
@InjectClass(injectClass = "javax/servlet/http/HttpServlet",
        injectMethod = {"service (Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V"})
public class HttpServletRequestPlugin extends BaseAdviceAdapter {

    private int startFlagLocal;

    public HttpServletRequestPlugin(MethodVisitor mv, int access, String name, String desc, InjectClassInfo injectClassInfo) {
        super(mv, access, name, desc, injectClassInfo);
        startFlagLocal = newLocal(Type.INT_TYPE);
    }

    @Override
    public void beforeMethodEnter() {
        // 常量0入栈
        mv.visitInsn(ICONST_0);
        // 常量0出栈并存入局部变量startFlagLocal
        storeLocal(startFlagLocal);
    }

    @Override
    public void afterMethodEnter() {
        // 将方法返回值出栈并存入局部变量startFlagLocal
        storeLocal(startFlagLocal);
    }

    @Override
    public void loadArgsOnMethodBegin() {
        // 方法第一个参数入栈
        loadArg(0);
    }

    @Override
    public void loadArgsOnMethodEnd() {
        // 开始标志入栈
        loadLocal(startFlagLocal);
        // 方法参数入栈
        loadArgs();
        // 正常return 异常为空
        push((Type) null);
    }

    @Override
    public void loadArgsOnThrow() {
        // 将异常对象复制并压栈
        dup();
        // 新建Throwable类型的局部变量
        int uncaughtExceptionLocal = newLocal(Type.getType(Throwable.class));
        // 异常对象出栈, 存入对应的局部变量表
        storeLocal(uncaughtExceptionLocal, Type.getType(Throwable.class));

        // 开始标志入栈
        loadLocal(startFlagLocal);
        // 方法参数入栈
        loadArgs();
        // 局部变量表中的异常对象入栈
        loadLocal(uncaughtExceptionLocal);
    }

    @InjectBeginMethod
    @SuppressWarnings("unused")
    public static int startRequest(Object request) {
        try {
            if (Tracer.getInstance() == null || Tracer.getInstance().getCurrentSpan() != null) {
                return 0;
            }
//            if (AgentMain.TYPE_TOMCAT_WAR.equals(AgentMain.agentType)) {
//                appId = Strings.nullToEmpty((String) MethodUtils.invokeMethod(request, "getContextPath"));
//                if (appId.startsWith("/")) {
//                    appId = appId.replaceAll("/", "");
//                }
//            }
            Tracer tracer = Tracer.getInstance();
            final Span parentSpan = HttpSpanFactory.fromHttpServletRequest(request);

            if (parentSpan != null) {
                tracer.startRequestWithChildSpan(parentSpan, HttpSpanFactory.getServletSpanName(request));
            } else {
                tracer.startRequestWithRootSpan(HttpSpanFactory.getServletSpanName(request));
            }
            return 1;
        } catch (Exception e) {
            Logger.error("HttpServletRequestPlugin.startRequest(" + request + ")", e);
        }
        return 0;
    }

    @InjectEndMethod
    @SuppressWarnings("unused")
    public static void completeRequestSpan(int startFlag, Object req, Object resp, Object uncaughtExceptionObj) {

        String url = "";
        String method = "";
        String queryString = "";
        int respCode = 0;
        Throwable uncaughtException = null;
        try {
            method = Strings.nullToEmpty(HttpServletReflectUtils.getMethod(req));
            String servletPath = Strings.nullToEmpty(HttpServletReflectUtils.getServletPath(req));
            String requestURI = Strings.nullToEmpty(HttpServletReflectUtils.getRequestURI(req));
            String contextPath = Strings.nullToEmpty(HttpServletReflectUtils.getContextPath(req));
            queryString = Strings.nullToEmpty(HttpServletReflectUtils.getQueryString(req));
            if (Strings.isNullOrEmpty(servletPath)) {
                if (requestURI.length() > 0 && contextPath.length() > 0 && contextPath.length() < requestURI.length()) {
                    url = requestURI.substring(contextPath.length(), requestURI.length());
                }
            } else {
                url = servletPath;
            }
            respCode = HttpServletReflectUtils.getStatus(resp);

            uncaughtException = (Throwable) uncaughtExceptionObj;

            if (respCode == 0) {
                respCode = 200;
            }
        } catch (Exception e) {
            Logger.error("HttpServletRequestPlugin.completeRequestSpan(" + startFlag + ", " + req + ", " + resp + ", " + uncaughtExceptionObj + ")", e);
        } finally {
            Map<String, Object> map = new HashMap<>();
            map.put("url", url);
            map.put("method", method);
            map.put("queryString", queryString);
            map.put("statusCode", respCode);
            Tracer.getInstance().completeRequestSpan(uncaughtException, map);
        }
    }
}
